package com.ls.fw.data.exception;

import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import com.ls.fw.data.rest.RestStatus;

/**
 * 异常
 * @author ls 2014年11月21日下午2:13:15
 */
public class DataException extends RuntimeException {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * 异常编码
	 */
	public enum ErrorCode {
		BAD_REQUEST(400), UNAUTHORIZED(401), FORBIDDEN(403), NOT_FOUND(404), CONFLICT(
				409), UNSUPPORTED_MEDIA_TYPE(415), SERVER_ERROR(500), SERVICE_UNAVAILABLE(
				503), UNKNOWN(0);
		public final int code;

		private ErrorCode(int c) {
			code = c;
		}

		public static ErrorCode getErrorCode(int c) {
			for (ErrorCode err : values()) {
				if (err.code == c)
					return err;
			}
			return UNKNOWN;
		}
	};

	public DataException(String msg) {
		super(msg);
	}

	public DataException(String msg, Throwable th) {
		super(msg, th);
	}

	public DataException(Throwable th) {
		super(th);
	}

	public DataException(ErrorCode code, String msg) {
		super(msg);
		this.code = code.code;
	}

	public DataException(ErrorCode code, String msg, Throwable th) {
		super(msg, th);
		this.code = code.code;
	}

	public DataException(ErrorCode code, Throwable th) {
		super(th);
		this.code = code.code;
	}

	protected DataException(int code, String msg, Throwable th) {
		super(msg, th);
		this.code = code;
	}

	int code = 0;

	public int code() {
		return code;
	}

	public void log(Logger log) {
		log(log, this);
	}

	public static void log(Logger log, Throwable e) {
		String stackTrace = toStr(e);
		String ignore = doIgnore(e, stackTrace);
		if (ignore != null) {
			log.info(ignore);
			return;
		}
		log.error(stackTrace);

	}

	public static void log(Logger log, String msg, Throwable e) {
		String stackTrace = msg + ':' + toStr(e);
		String ignore = doIgnore(e, stackTrace);
		if (ignore != null) {
			log.info(ignore);
			return;
		}
		log.error(stackTrace);
	}

	public static void log(Logger log, String msg) {
		String stackTrace = msg;
		String ignore = doIgnore(null, stackTrace);
		if (ignore != null) {
			log.info(ignore);
			return;
		}
		log.error(stackTrace);
	}

	// public String toString() { return toStr(this); } // oops, inf loop
	@Override
	public String toString() {
		return super.toString();
	}

	public static String toStr(Throwable e) {
		CharArrayWriter cw = new CharArrayWriter();
		PrintWriter pw = new PrintWriter(cw);
		e.printStackTrace(pw);
		pw.flush();
		return cw.toString();

		/**
		 * This doesn't work for some reason!!!!! StringWriter sw = new
		 * StringWriter(); PrintWriter pw = new PrintWriter(sw);
		 * e.printStackTrace(pw); pw.flush(); System.out.println("The STRING:" +
		 * sw.toString()); return sw.toString();
		 **/
	}

	/**
	 * For test code - do not log exceptions that match any of the regular
	 * expressions in ignorePatterns
	 */
	public static Set<String> ignorePatterns;

	/**
	 * Returns null if this exception does not match any ignore patterns, or a
	 * message string to use if it does.
	 */
	public static String doIgnore(Throwable t, String m) {
		if (ignorePatterns == null || m == null)
			return null;
		if (t != null && t instanceof AssertionError)
			return null;

		for (String regex : ignorePatterns) {
			Pattern pattern = Pattern.compile(regex);
			Matcher matcher = pattern.matcher(m);

			if (matcher.find())
				return "Ignoring exception matching " + regex;
		}

		return null;
	}

	public static Throwable getRootCause(Throwable t) {
		while (true) {
			Throwable cause = t.getCause();
			if (cause != null) {
				t = cause;
			} else {
				break;
			}
		}
		return t;
	}

	/**
	 * Unwraps the actual cause from the exception for cases when the exception
	 * is a {@link ElasticDataWrapperException}.
	 *
	 * @see org.elasticData.ExceptionsHelper#unwrapCause(Throwable)
	 */
	public Throwable unwrapCause() {
		return ExceptionsHelper.unwrapCause(this);
	}

	/**
	 * Returns the rest status code associated with this exception.
	 */
	public RestStatus status() {
		Throwable cause = unwrapCause();
		if (cause == this) {
			return RestStatus.INTERNAL_SERVER_ERROR;
		} else if (cause instanceof DataException) {
			return ((DataException) cause).status();
		} else if (cause instanceof IllegalArgumentException) {
			return RestStatus.BAD_REQUEST;
		} else {
			return RestStatus.INTERNAL_SERVER_ERROR;
		}
	}

	/**
	 * Return the detail message, including the message from the nested
	 * exception if there is one.
	 */
	public String getDetailedMessage() {
		if (getCause() != null) {
			StringBuilder sb = new StringBuilder();
			sb.append(toString()).append("; ");
			if (getCause() instanceof DataException) {
				sb.append(((DataException) getCause()).getDetailedMessage());
			} else {
				sb.append(getCause());
			}
			return sb.toString();
		} else {
			return super.toString();
		}
	}

	/**
	 * Retrieve the innermost cause of this exception, if none, returns the
	 * current exception.
	 */
	public Throwable getRootCause() {
		Throwable rootCause = this;
		Throwable cause = getCause();
		while (cause != null && cause != rootCause) {
			rootCause = cause;
			cause = cause.getCause();
		}
		return rootCause;
	}

	/**
	 * Retrieve the most specific cause of this exception, that is, either the
	 * innermost cause (root cause) or this exception itself.
	 * <p>
	 * Differs from {@link #getRootCause()} in that it falls back to the present
	 * exception if there is no root cause.
	 *
	 * @return the most specific cause (never <code>null</code>)
	 */
	public Throwable getMostSpecificCause() {
		Throwable rootCause = getRootCause();
		return (rootCause != null ? rootCause : this);
	}

	/**
	 * Check whether this exception contains an exception of the given type:
	 * either it is of the given class itself or it contains a nested cause of
	 * the given type.
	 *
	 * @param exType
	 *            the exception type to look for
	 * @return whether there is a nested exception of the specified type
	 */
	public boolean contains(Class<?> exType) {
		if (exType == null) {
			return false;
		}
		if (exType.isInstance(this)) {
			return true;
		}
		Throwable cause = getCause();
		if (cause == this) {
			return false;
		}
		if (cause instanceof DataException) {
			return ((DataException) cause).contains(exType);
		} else {
			while (cause != null) {
				if (exType.isInstance(cause)) {
					return true;
				}
				if (cause.getCause() == cause) {
					break;
				}
				cause = cause.getCause();
			}
			return false;
		}
	}
}